﻿using Codemonk.Common.Caching;
using Codemonk.Common.Configuring;
using Codemonk.Common.Data;
using Codemonk.Common.Emit;
using Codemonk.Common.Logging;
using Codemonk.Common.ObjectModel;
using System;
using System.Collections.Generic;
using System.Xml;
namespace Codemonk.Common.Injection
{
    internal class ObjectBuilder : Configurable, IObjectBuilder, IConfigurable
    {
        private static ObjectBuilder instance;

        internal static ObjectBuilder Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (sync)
                    {
                        if (instance == null) instance = new ObjectBuilder();
                    }
                }
                return instance;
            }
        }

        private ObjectBuilder() { }

        private static ICacheCollection<Type, Func<object>> systemDefaultObject = new DictionaryCache<Type, Func<object>>();

        static ObjectBuilder()
        {
            systemDefaultObject.Set(typeof(ILogFactory), () => new DiagnosticLogFactory());
            systemDefaultObject.Set(typeof(ILog), () => new DiagnosticLog());
            systemDefaultObject.Set(typeof(ITransactionContext), () => new TransactionContext());
            systemDefaultObject.Set(typeof(IObjectBuilder), () => new ObjectBuilder());
            systemDefaultObject.Set(typeof(ITypeAccessorFactory), () => new TypeAccessorFactory());
            systemDefaultObject.Set(typeof(IConfigReader), () => new ConfigReader());
            systemDefaultObject.Set(typeof(IDbAccessor), () => new DbAccessor());
            systemDefaultObject.Set(typeof(IDbAccessorFactory), () =>
            {
                DbAccessorFactory factory = null;
                Func<DbAccessorFactory> func = new Func<DbAccessorFactory>(() =>
                {
                    if (factory == null) factory = new DbAccessorFactory();
                    return factory;
                });
                return func();
            });
        }

        /// <summary>
        /// 变量类型
        /// </summary>
        private enum ValueKind : byte { Constant = 0, Variable = 1, Unknown = 2 }
        private static readonly object sync = new object();
        private ICacheCollection<string, ValueKind> valueNames = new DictionaryCache<string, ValueKind>();
        private ICacheCollection<string, object> constants = new DictionaryCache<string, object>();
        private ICacheCollection<string, Func<object>> variables = new DictionaryCache<string, Func<object>>();
        private ITypeAccessorFactory accessorFactory = new TypeAccessorFactory();
        private bool initializated = false;
        public override void Initialization()
        {
            try
            {
                ReadAllName();
            }
            catch { }
        }

        private void ReadAllName()
        {
            ICacheCollection<string, ValueKind> tmpValueNames = new DictionaryCache<string, ValueKind>();
            var constantNames = Reader.Read("/codemonk/constants//constant/name");
            List<string> names = new List<string>();
            foreach (var constantName in constantNames)
            {
                if (!string.IsNullOrWhiteSpace(constantName))
                    tmpValueNames.Set(constantName.Trim(), ValueKind.Constant);
            }
            var variableNames = Reader.Read("/codemonk/variables//variable/name");
            names = new List<string>();
            foreach (var variableName in variableNames)
            {
                if (!string.IsNullOrWhiteSpace(variableName))
                    tmpValueNames.Set(variableName.Trim(), ValueKind.Variable);
            }
            valueNames.Clear();
            valueNames.Set(tmpValueNames);
            constants.Clear();
            variables.Clear();
        }

        private ValueKind GetValueKindByName(string name)
        {
            ValueKind kind;
            if (valueNames.TryGet(name, out kind)) return kind;
            return ValueKind.Unknown;
        }

        private Func<object> ReadValue(string xpath)
        {
            try
            {
                var node = Reader.ReadAsNode(xpath);
                if (node == null) return () => null;
                var firstchild = node.FirstChild;
                if (firstchild == null) return () => null;
                var name = firstchild.Name.Trim();
                if (name == "simplevalue") return () => ReadSimpleValue(firstchild.ReadFirst("@type"), firstchild.ReadFirst("@value"));
                if (name == "complexvalue") return ReadComplexValue(firstchild);
                return () => null;
            }
            catch { return () => null; }
        }

        #region ComplexValue

        private Func<object> ReadComplexValue(XmlNode node)
        {
            var classpath = node.ReadFirst("classpath");
            if (string.IsNullOrWhiteSpace(classpath)) return () => null;
            var type = Type.GetType(classpath);
            if (type == null) return () => null;
            var accessor = accessorFactory.GetByType(type);
            var ctor = node.ReadAsNode("injections/ctor");
            List<object> injectedCtor = new List<object>();
            if (ctor != null)
            {
                var ctorParameters = ctor.ReadComplex("parameters/parameter");
                if (ctorParameters != null)
                {
                    foreach (var ctorParameter in ctorParameters)
                        injectedCtor.Add(ReadParameter(ctorParameter));
                }
            }
            var injectedCtorArray = injectedCtor.ToArray();
            List<Tuple<string, object>> injectedProperties = new List<Tuple<string, object>>();
            var properties = node.ReadComplex("injections/properties//property");
            if (properties != null)
            {
                foreach (var property in properties)
                {
                    var tuple = ReadProperty(property);
                    if (tuple != null) injectedProperties.Add(tuple);
                }
            }
            if (injectedProperties.Count != 0)
            {
                return () =>
                {
                    var obj = accessor.Create(injectedCtorArray);
                    foreach (var item in injectedProperties)
                        accessor.SetProperty(obj, item.Item1, item.Item2);
                    return obj;
                };
            }
            else
            {
                return () => accessor.Create(injectedCtorArray);
            }
        }

        private Tuple<string, object> ReadProperty(XmlNode node)
        {
            if (node == null) return null;
            var name = node.ReadFirst("property/@name");
            if (string.IsNullOrWhiteSpace(name)) return null;
            var value = node.ReadFirst("property/@value");
            var type = node.ReadFirst("property/@type");
            var obj = string.IsNullOrWhiteSpace(type) ? Resolve(value) : ReadSimpleValue(type, value);
            return new Tuple<string, object>(name, obj);
        }

        private object ReadParameter(XmlNode node)
        {
            if (node == null) return null;
            var value = node.ReadFirst("parameter/@value");
            var type = node.ReadFirst("parameter/@type");
            return string.IsNullOrWhiteSpace(type) ? Resolve(value) : ReadSimpleValue(type, value);
        }

        #endregion

        #region SimpleValue

        private static Func<string, object> SafetyConvert<T>(Func<string, T> converter)
        {
            return s =>
            {
                try
                {
                    return converter(s);
                }
                catch { return default(T); }
            };
        }

        #region simpleValueConverters


        private static Dictionary<string, Func<string, object>> simpleValueConverters =
            new Dictionary<string, Func<string, object>> {
            {"sbyte",SafetyConvert(sbyte.Parse) } ,
            {"byte",  SafetyConvert(byte.Parse) } ,
            {"short", SafetyConvert(short.Parse) } ,
            {"ushort",SafetyConvert(ushort.Parse) } ,
            {"int", SafetyConvert(int.Parse)   } ,
            {"uint", SafetyConvert(uint.Parse)  } ,
            {"long",  SafetyConvert(long.Parse) } ,
            {"ulong",SafetyConvert(ulong.Parse)  } ,
            {"char", SafetyConvert(char.Parse)  } ,
            {"float",SafetyConvert(float.Parse)  } ,
            {"double",SafetyConvert(double.Parse) } ,
            {"bool ",SafetyConvert(bool.Parse)  } ,
            {"string",s=>s } ,
            {"object",s=>null },
            {"ipaddress",SafetyConvert(s=>s.Trim() == "localhost"? System.Net.IPAddress.Loopback: System.Net.IPAddress.Parse(s))},
            {"ipendpoint",SafetyConvert(s=>{
                var array = s.Split(new []{':'});
                if (array.Length != 2) return null;
                System.Net.IPAddress ipaddress=array[0].Trim() == "localhost"? System.Net.IPAddress.Loopback: System.Net.IPAddress.Parse(array[0]);
                return new System.Net.IPEndPoint(ipaddress,int.Parse(array[1]));
            })}, };

        #endregion

        private object ReadSimpleValue(string type, string value)
        {
            Func<string, object> converter;
            if (simpleValueConverters.TryGetValue(type, out converter)) return converter(value);
            return null;
        }

        #endregion

        private object ReadConstant(string name)
        {
            object obj;
            if (constants.TryGet(name, out obj)) return obj;
            Func<object> creater = ReadValue(string.Concat("/codemonk/constants/constant[name='", name, "']/value"));
            if (creater != null) obj = creater();
            constants.Set(name, obj);
            return obj;
        }

        private object ReadVariable(string name)
        {
            Func<object> creater;
            if (variables.TryGet(name, out creater)) return creater();
            creater = ReadValue(string.Concat("/codemonk/variables/variable[name='", name, "']/value"));
            if (creater != null) variables.Set(name, creater);
            else variables.Set(name, () => null);
            return creater();
        }

        /// <summary>
        /// 动态解析对象
        /// 如果对象是常量,如果缓存中这个量则直接返回这个量,否则动态读取xml,并构造这个变量
        /// 如果对象是变量,则将存储一个对象的创建方法,如果这个方法已经在缓存中则直接调用这个方法创建对象,否则构造这个方法
        /// </summary>
        /// <param name="name">对象名称</param>
        /// <returns>对象</returns>
        public object Resolve(string name)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(name)) return null;
                if (initializated == false) { initializated = true; Initialization(); }
                ValueKind kind = GetValueKindByName(name);
                if (kind == ValueKind.Unknown) return null;
                return kind == ValueKind.Constant ? ReadConstant(name) : ReadVariable(name);
            }
            catch { return null; }
        }

        public override IConfigReader Reader
        {
            get
            {
                return base.Reader == null ? (base.Reader = new TypeConfigReader { EnableMonitoring = true }) : base.Reader;
            }
            set
            {
                base.Reader = value;
            }
        }

        public T Resolve<T>(string name = null)
        {
            var obj = Resolve(name);
            if (obj == null)
            {
                Func<object> func;
                if (systemDefaultObject.TryGet(typeof(T), out func))
                    return (T)func();
                return default(T);
            }
            return (T)obj;
        }
    }
}
